#include <iostream>
#include <string>
#include <unistd.h>
#include <vector>
#include <cstdlib>
#include <functional>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"
#include "Channel.hpp"
using namespace std;

using work_t = function<void()>;

// 错误码
enum
{
    OK = 0,
    UsageError,
    PipeError,
    ForkError
};

class ProcessPool
{
public:
    // 构造
    ProcessPool(int n, work_t w)
        :processnum(n)
        ,work(w)
    {}

    int InitProcessPool() 
    {
        for (int i = 0; i < processnum; i++)
        {
            // 1.先有管道
            int pipefd[2] = {0}; // 创建读写端数组
            int n = pipe(pipefd);
            if (n < 0) // 返回了 -1，说明失败了，同时被设置错误码
                return PipeError;
            // 2.创建进程
            pid_t id = fork();
            if (id < 0) // 创建子进程失败，返回-1
                return ForkError;
            // 3.建立通信信道
            if (id == 0)
            {
                // 我是子进程
                ::close(pipefd[1]); // 子进程要读，所以关闭 写
                dup2(pipefd[0], 0); // 将从管道读端里读，重定向到从 cin 里读，这样子进程执行的事情就不需要传文件描述符了
                work();             // 每个子进程各自执行它的事情
                ::exit(0);          // 最后都要退出
            }
            // 我是父进程
            ::close(pipefd[0]);                   // 父进程要写，所以关闭 读
            channels.emplace_back(pipefd[1], id); // 传一个 Channel 对象，第一个参数为要写的东西（写端），第二个参数为子进程的 pid
        }
        return OK;
    }

    void DispatchTask()
    {
        int who = 0;
        int num = 20; // 一共派发 num 个任务
        while (num--)
        {
            // a. 选择一个任务，整数
            int task = t.SelectTask();
            // b. 选择一个子进程 channel
            Channel &curr = channels[who++];
            who %= channels.size();

            cout << "########################" << endl;
            cout << "send " << task << " to " << curr.Name() << " 任务还剩：" << num << endl;
            cout << "########################" << endl;

            // c. 派发任务
            curr.Send(task);

            sleep(1);
        }
    }

    void CleanProcessPool()
    {
        for (auto &c : channels) // 先全部关闭
            c.Close();
        // 父进程回收僵尸
        for (auto &c : channels)
        {
            pid_t rid = ::waitpid(c.Id(), nullptr, 0);
            if (rid > 0) // 退出成功
            {
                cout << "child " << "wait ... success" << endl;
            }
        }
    }
private:
    vector<Channel> channels;
    int processnum;  // 以后要执行多少个子进程
    work_t work;  // 子进程未来要执行的任务：回调函数，通过function<void()>，可以接受任意类型的函数方法，所以我们想让子进程执行什么方法，只需要传对应的函数名，就可以实现回调
};